/**
 * Copyright (c) 2010 Basil Shikin, BugStat Project
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.bugstat.exporters;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bugstat.common.bugs.Component;
import org.bugstat.common.bugs.Platform;
import org.bugstat.common.bugs.Priority;
import org.bugstat.common.bugs.Product;
import org.bugstat.common.bugs.Resolution;
import org.bugstat.common.bugs.Severity;
import org.bugstat.common.bugs.Status;
import org.bugstat.common.bugs.User;

/**
 * This class is used to write temporal information about a bug into a file. Temporal information is information that changes with time. 
 * This is bug status, assignee, number of comments, etc. 
 * 
 * <p>
 * <h3>Temporal information file format</h3>
 * Bug temporal information files contain information about all bugs in the system on a given date. Usual file name would identify
 * the date. Bug temporal information files have following structure:
 * <pre>
 *          +-------------+
 *   line 0 | FILE_HEADER |
 *   line 1 | BUG 1       |
 *   line 2 | BUG 2       |
 *              .  .  .
 *   line N | BUG N       |
 *          +-------------+
 * </pre>
 * Note that line number is used for bug ID. So, complete bug state of bug 525 on 9th of May could be found in file 05-09-2010 on line 525.
 * 
 * <h3>Temporal information header format</h3>
 * Temporal information header contains information about type of current temporal info file. It has following format:  
 * <pre>
 *     VVVV LLLL TTTTTTTT D
 *     0    4    8        16
 * </pre>
 * <b>Offset 0 (V)</b> <i>[int]</i> 4 bytes that identify version of current file.<br> 
 * <b>Offset 4 (L)</b> <i>[int]</i> 4 bytes for length of each bug tempral record.<br> 
 * <b>Offset 8 (T)</b> <i>[long]</i> 8 bytes for the time stamp (when file was last modified). 
 * <b>Offset 16 (D)</b> <i>[byte]</i> 1 byte for delimiter (new line).<br> 
 * 
 * <h3>Bug temporal record format</h3>
 * Bug temporal information is written as a sequence of bytes terminated with a new line (\n character ). Following binary format is used:
 * <pre>
 *     AA S R P V MM L CC NN D
 *     0  2 3 4 5 6  7 8  9  11
 * </pre>
 * <b>Offset 0 (A)</b> <i>[ref]</i>  2 bytes for bug owner (reference to <code>User</code> object).<br>  
 * <b>Offset 2 (S)</b> <i>[byte]</i> 1 byte for status.<br>
 * <b>Offset 3 (R)</b> <i>[byte]</i> 1 byte for resolution.<br>
 * <b>Offset 4 (P)</b> <i>[byte]</i> 1 byte for priority.<br> 
 * <b>Offset 5 (V)</b> <i>[byte]</i> 1 byte for severity.<br>
 * <b>Offset 6 (L)</b> <i>[byte]</i> 1 byte for platform.<br>
 * <b>Offset 7 (C)</b> <i>[ref]</i> 2 bytes for component (reference to <code>Component</code> object).<br>
 * <b>Offset 9 (N)</b> <i>[ref]</i> 2 bytes for product (reference to <code>Product</code> object).<br>
 * <b>Offset 11 (N)</b> <i>[short]</i> 2 bytes number of comments.<br>
 * <b>Offset 13 (D)</b> <i>[byte]</i> 1 byte for delimiter (new line).<br>
 *  
 * @author Basil Shikin
 *
 */
public class BugTemporalInfoWriter
{
    private static Log log = LogFactory.getLog( BugTemporalInfoWriter.class );
    
    public static final int    RECORD_LENGTH = 14;
    public static final int    HEADER_LENGTH = 18;
    public static final String VERSION       = "1.00";
    
    private static final byte   DELIMITER = '\n';
    
    private final UserReferenceStorage    userReferenceStorage;
    private final ComponentReferenceStorage componentReferenceStorager;
    private final ProductReferenceStorage   productReferenceStorage;
    
    /**
     * 
     * @param userReferenceStorage
     * @param componentReferenceStorage
     */
    public BugTemporalInfoWriter( UserReferenceStorage userReferenceStorage, 
                                  ComponentReferenceStorage componentReferenceStorage,
                                  ProductReferenceStorage productReferenceStorage )
    {
        if ( userReferenceStorage == null )      throw new IllegalArgumentException("Invalid user reference manager specified");
        if ( componentReferenceStorage == null ) throw new IllegalArgumentException("Invalid component reference manager specified");
        
        this.userReferenceStorage       = userReferenceStorage;
        this.componentReferenceStorager = componentReferenceStorage;
        this.productReferenceStorage    = productReferenceStorage;
    }

    /**
     * Write header file of the bug file. This header is used to identify writer version
     * 
     * @param out Output stream to write header into. Must not be null.
     *  
     * @throws IOException In case unable to write.
     */
    public void writeHeader( RecordStorage file ) throws IOException
    {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream( 17 );
        final DataOutputStream      dos  = new DataOutputStream( baos );
        dos.writeBytes( VERSION );
        dos.writeInt( RECORD_LENGTH );
        dos.writeLong( System.currentTimeMillis() );
        dos.writeChar( DELIMITER );
        
        file.writeHeader( baos.toByteArray() );
        
        dos.close();
        baos.close();
    }
    
    
    /**
     * Append temporal information about the new bug into given bug file.
     * 
     * @param bugID  ID of bug to append. Must be greater then zero
     * @param file   File to write bug into. Must not be null.
     * 
     * @throws IOException In case unable to write.
     */
    public void appendBugInfo( int bugID, final RecordStorage file ) throws IOException
    {
        if ( bugID < 1 )    throw new IllegalArgumentException("Invalid bug ID specified");
        if ( file == null ) throw new IllegalArgumentException("Invalid file specified");
        
        if ( !file.containsRecord( bugID ) )
        {
            // Append new bug
            final int appendedBugID = file.appendRecord();
            if ( appendedBugID != bugID ) throw new RuntimeException("ID of appended bug is not equal to requested bug ID");
            
            // Append bug delimiter
            writeDelimiter( bugID, file );
            
            log.debug("Appended bug " + bugID );
        }
        else
        {
            log.warn("Trying to append existing bug record (bug id is " + bugID + ")");
        }
        
    }
    
    public void writeAssignedTo(int bugID, RecordStorage file, User user ) throws IOException
    {
        file.writeShort(bugID, 0, userReferenceStorage.getReferenceId( user ) );
    }
    
    public void writeStatus(int bugID, RecordStorage file, Status status ) throws IOException
    {
        file.writeByte(bugID, 2, Status.toByte( status ) );
    }
    
    public void writeResolution(int bugID, RecordStorage file, Resolution resolution ) throws IOException
    {
        file.writeByte(bugID, 3, Resolution.toByte( resolution ) );
    }
    
    public void writePriority(int bugID, RecordStorage file, Priority priority ) throws IOException
    {
        file.writeByte(bugID, 4, Priority.toByte( priority ) );
    }
    
    public void writeSeverity(int bugID, RecordStorage file, Severity severity ) throws IOException
    {
        file.writeByte(bugID, 5, Severity.toByte( severity ) );
    }

    public void writePlatform(int bugID, RecordStorage file, Platform platform ) throws IOException
    {
        file.writeByte(bugID, 6, Platform.toByte( platform ) );
    }
    
    public void writeComponent(int bugID, RecordStorage file, Component component ) throws IOException
    {
        file.writeShort(bugID, 7, componentReferenceStorager.getReferenceId( component ) );
    }
    
    public void writeProduct(int bugID, RecordStorage file, Product product ) throws IOException
    {
        file.writeShort(bugID, 9, productReferenceStorage.getReferenceId( product ) );
    }
    
    public void writeCommentsNumber(int bugID, RecordStorage file, short commentsNumber) throws IOException
    {
        file.writeShort(bugID, 11, commentsNumber );
    }
    
    public void writeDelimiter(int bugID, RecordStorage file ) throws IOException
    {
        file.writeByte(bugID, RECORD_LENGTH - 1, DELIMITER );
    }
}
